﻿using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Common.Core.Exceptions;
using Newtonsoft.Json;
using Simple.Common.Core.Config;

namespace Simple.Common.Cache
{
    public partial class RedisService: IRedisService
    {
        private readonly IDatabase _database;
        /// <summary>
        /// 缓存区。
        /// </summary>
        public  string Region { get; }

        private readonly RedisValue _lockValue;

        /// <summary>
        /// Redis缓存服务器。
        /// </summary>
        /// <param name="region">缓存区域。</param>
        /// <param name="connectString">连接字符串（未指定时，从配置文件中读取名为“redis”的字符串作为链接）。</param>
        /// <param name="defaultDb">默认db。</param>
        public RedisService(string region, string connectString = null, int defaultDb = -1)
        {
            Region = region ?? throw new ArgumentNullException(nameof(region));

            _lockValue = new RedisValue(region);

            _database = RedisManager.Instance.GetDatabase(ConfigurationHelper.GetValue("Redis:Configuration"), ConfigurationHelper.GetValue<int>("Redis:DefaultDb"));
        }

        /// <summary> 
        /// 获取缓存键。 
        /// </summary>
        /// <param name="key">键。</param>
        /// <returns></returns>
        protected virtual string GetKey(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException($"“{nameof(key)}”不能是 Null 或为空", nameof(key));
            }

            return string.Concat(Region, ":", key); //, "@", key.GetHashCode()
        }
        /// <summary>
        /// 过期时间
        /// </summary>
        private readonly  TimeSpan _expireTimeSpan= TimeSpan.FromHours(24);

        /// <summary>
        /// 设置缓存过期时间（有限时间）。
        /// </summary>
        /// <param name="key">键。</param>
        /// <param name="expireTimeSpan">有效期。</param>
        /// <returns></returns>
        public async Task<bool> ExpireAsync(string key, TimeSpan? expireTimeSpan = null) => await _database.KeyExpireAsync(GetKey(key), expireTimeSpan);
        /// <summary>
        /// 清空缓存。
        /// </summary>
        /// <returns></returns>
        public async Task ClearAsync(string param="")
        {
            var script = LuaScript.Prepare("return redis.call('DEL',unpack(redis.call('KEYS', @keypattern)))");
            var key = GetKey($"{param}:*");
            var result=await  _database.ScriptEvaluateAsync(script, new { @keypattern = key });
            await Task.CompletedTask;
        }
        /// <summary>
        /// 加锁。
        /// </summary>
        /// <param name="key">键。</param>
        /// <param name="timeSpan">锁定时间（默认：10s）。</param>
        public async Task<bool> TryEnterLockAsync(string key, TimeSpan? timeSpan = null)=>await _database.LockTakeAsync(GetKey(key), _lockValue, timeSpan ?? TimeSpan.FromSeconds(10));
        /// <summary>
        /// 释放锁。
        /// </summary>
        /// <param name="key">键。</param>
        public async Task ExitLockAsync(string key) =>await _database.LockReleaseAsync(GetKey(key), _lockValue);

        /// <summary>
        /// 获取分布式锁并执行Action（超时会自动释放）
        /// </summary>
        /// <param name="lockKey">锁key</param>
        /// <param name="func">执行方法</param>
        /// <param name="timeSpan">自动释放时间，默认10秒</param>
        /// <returns></returns>
        public async Task<bool> LockAndRun(string lockKey, Func<Task> func, TimeSpan? timeSpan = null)
        {
            //RedisValue Token = Environment.MachineName;
            var rediskey = GetKey(lockKey);
            if (await _database.LockTakeAsync(rediskey, _lockValue, timeSpan ?? TimeSpan.FromSeconds(10)))
            {
                try
                {
                    await func();
                    return true;
                }
                catch (Exception e)
                {
                    throw new BusinessException("操作异常，请联系管理员处理", new Exception($"分布式锁内部执行异常-{e.Message}"));
                }
                finally
                {
                    await _database.LockReleaseAsync(rediskey, _lockValue);
                }
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 获取分布式锁并执行Action（超时会自动释放）
        /// </summary>
        /// <typeparam name="T">返回类型</typeparam>
        /// <param name="lockKey">锁key</param>
        /// <param name="func">执行方法</param>
        /// <param name="errorMsg">未取得锁时异常消息</param>
        /// <param name="timeSpan">自动释放时间，默认10秒</param>
        /// <returns></returns>
        public async Task<T> LockAndRun<T>(string lockKey, Func<Task<T>> func, string errorMsg, TimeSpan? timeSpan = null)
        {
            //RedisValue Token = Environment.MachineName;
            var rediskey = GetKey(lockKey);

            if (await _database.LockTakeAsync(rediskey, _lockValue, timeSpan ?? TimeSpan.FromSeconds(10)))
            {
                try
                {
                    var result = await func();
                    return result;
                }
                catch (Exception e)
                {
                    throw new BusinessException("操作异常，请联系管理员处理", new Exception($"分布式锁内部执行异常-{e.Message}"));
                }
                finally
                {
                    await _database.LockReleaseAsync(rediskey, _lockValue);
                }
            }
            throw new BusinessException("操作异常，请联系管理员处理",new Exception("获取分布式锁失败"));
        }
    }
}